#include "leveleditor.h"
#include "ui_leveleditor.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QInputDialog>
#include "yamlstream.h"
#include <Shinygami/datatree.hpp>
#include <QTextStream>
#include <QDebug>
#include <QProcess>
#include <QDebug>
#include <QListWidgetItem>
#include <QMessageBox>
#include <iostream>

#ifdef WIN32
# define APPLEJACK_SAMPLER_EXE "./applejackSampler.exe"
#else
# define APPLEJACK_SAMPLER_EXE "./applejackSampler"
#endif

LevelEditor::LevelEditor(QWidget *parent) : QWidget(parent), ui(new Ui::LevelEditor)
{
    ui->setupUi(this);
    ui->shapeEditor->show();
    ui->shapeEditor->setEnabled(false);
    ui->shapeEditor->SetTextureManager(&_textureManager);
    ui->shapeEditor->SetAnimationManager(&_animationManager);
    ui->animationEditor->SetTextureManager(&_textureManager);
    ui->animationEditor->SetAnimationManager(&_animationManager);
    ui->levelPreview->SetTextureManager(&_textureManager);
    ui->levelPreview->SetAnimationManager(&_animationManager);
    ui->levelPreview->SetObjectManager(&_objectManager);

    connect(ui->reloadTextures,   SIGNAL(clicked()), this, SLOT(LoadTextures()));
    connect(ui->reloadAnimations, SIGNAL(clicked()), this, SLOT(LoadAnimations()));
    connect(ui->reloadObjects,    SIGNAL(clicked()), this, SLOT(LoadObjects()));

    connect(ui->buttonImportTexture, SIGNAL(clicked()),        this,            SLOT(PickNewTexture()));
    connect(ui->buttonDeleteTexture, SIGNAL(clicked()),        this,            SLOT(DeleteTexture()));
    connect(&_textureManager, SIGNAL(TextureAdded(QString)),   this,            SLOT(TextureAdded(QString)));
    connect(&_textureManager, SIGNAL(TextureAdded(QString)),   ui->shapeEditor, SLOT(NewTexture(QString)));
    connect(&_textureManager, SIGNAL(TextureAdded(QString)),   this,            SLOT(TextureListUpdated()));
    connect(&_textureManager, SIGNAL(TextureDeleted(QString)), this,            SLOT(TextureListUpdated()));
    connect(&_textureManager, SIGNAL(TextureDeleted(QString)), ui->shapeEditor, SLOT(TextureDeleted(QString)));
    connect(&_textureManager, SIGNAL(TextureDeleted(QString)), this,            SLOT(TextureDeleted(QString)));
    connect(ui->listTextures, SIGNAL(currentRowChanged(int)),  this,            SLOT(TextureListIndexChanged(int)));

    connect(ui->addAnimation,    SIGNAL(clicked()),                   this,                SLOT(CreateNewAnimation()));
    connect(ui->deleteAnimation, SIGNAL(clicked()),                   this,                SLOT(DeleteAnimation()));
    connect(&_animationManager,  SIGNAL(AnimationAdded(QString)),     this,                SLOT(AddAnimationToList(QString)));
    connect(&_animationManager,  SIGNAL(AnimationAdded(QString)),     ui->shapeEditor,     SLOT(NewAnimation(QString)));
    connect(&_animationManager,  SIGNAL(AnimationDeleted(QString)),   this,                SLOT(AnimationDeleted(QString)));
    connect(&_animationManager,  SIGNAL(AnimationDeleted(QString)),   ui->shapeEditor,     SLOT(AnimationDeleted(QString)));
    connect(ui->listAnimation,   SIGNAL(currentTextChanged(QString)), ui->animationEditor, SLOT(LoadAnimation(QString)));

    connect(ui->collisionEditor, SIGNAL(GroupAdded(QString)),         ui->shapeEditor,     SLOT(NewCollisionGroup(QString)));
    connect(ui->collisionEditor, SIGNAL(GroupDeleted(QString)),       ui->shapeEditor,     SLOT(DeletedCollisionGroup(QString)));

    connect(ui->addObjeect,   SIGNAL(clicked()),               this, SLOT(CreateNewObject()));
    connect(ui->listObjects,  SIGNAL(currentRowChanged(int)),  this, SLOT(ObjectListIndexChanged(int)));
    connect(&_objectManager,  SIGNAL(Updated()),               this, SLOT(ObjectListUpdated()));

    connect(ui->worldLandscape, SIGNAL(currentIndexChanged(QString)), this, SLOT(LandscapeChanged(QString)));
    connect(ui->worldHeight,    SIGNAL(valueChanged(int)),            this, SLOT(UpdateLevelSize()));
    connect(ui->worldWidth,     SIGNAL(valueChanged(int)),            this, SLOT(UpdateLevelSize()));

    connect(ui->addEntity,    SIGNAL(clicked()), this, SLOT(AddEntity()));
    connect(ui->deleteObject, SIGNAL(clicked()), this, SLOT(DelEntity()));

    // Icons
    QIcon iconCatMain("icons/cat-main.png");
    QIcon iconCatLevelEditor("icons/cat-leveleditor.png");
    QIcon iconCatGraphisms("icons/cat-graphism.png");
    QIcon iconCatObjectEditor("icons/cat-objecteditor.png");
    QIcon iconAdd("icons/list-add.png");
    QIcon iconDel("icons/list-remove.png");

    ui->tabWidget->setTabIcon(0, iconCatMain);
    ui->tabWidget->setTabIcon(1, iconCatLevelEditor);
    ui->tabWidget->setTabIcon(2, iconCatGraphisms);
    ui->tabWidget->setTabIcon(3, iconCatObjectEditor);
    ui->addAnimation->setIcon(iconAdd);
    ui->addEntity->setIcon(iconAdd);
    ui->addObjeect->setIcon(iconAdd);
    ui->deleteObject->setIcon(iconDel);
    ui->buttonImportTexture->setIcon(iconAdd);
    ui->buttonDeleteTexture->setIcon(iconDel);
}

LevelEditor::~LevelEditor()
{
    delete ui;
}

QString GetPathFromFilepath(const QString&);

void LevelEditor::CreateNewAnimation()
{
    QString name = QInputDialog::getText(this, "Nouvelle animation", "Nom");

    if (name != "")
    {
      _animationManager.AddAnimation(name);
    }
}

void LevelEditor::DeleteAnimation()
{
    QListWidgetItem* item = ui->listAnimation->currentItem();

    if (item)
      _animationManager.DeleteAnimation(item->text());
}

void LevelEditor::AnimationDeleted(QString name)
{
    RemoveItemFromList(ui->listAnimation, name);
}

void LevelEditor::TextureDeleted(QString name)
{
    RemoveItemFromList(ui->listTextures, name);
}

void LevelEditor::RemoveItemFromList(QListWidget *list, QString name)
{
    int i    = 0;

    while (i < list->count())
    {
	QListWidgetItem* item = list->item(i);

	if (item->text() == name)
	{
	    list->removeItemWidget(item);
	    delete item;
	    break ;
	}
	else
	    ++i;
    }
}

void LevelEditor::AddAnimationToList(QString name)
{
    ui->listAnimation->addItem(name);
}

void LevelEditor::LandscapeChanged(QString str)
{
    ui->levelPreview->ChangeLandscape(_textureManager.GetByName(str));
}

void LevelEditor::AddEntity()
{
    ui->levelPreview->AddEntity(ui->listObjectToAdd->currentText());
}

void LevelEditor::DelEntity()
{
    if (ui->listObjects->count() > 0)
    {
        int ret = QMessageBox::warning(this, "Supprimer objet", "Sur ?", QMessageBox::Yes, QMessageBox::No);

        if (ret == QMessageBox::Yes)
        {
            QListWidgetItem* item = ui->listObjects->currentItem();
            QString          text = item->text();

            _objectManager.DelObject(text);
            ui->listObjects->removeItemWidget(item);
            delete item;
            if (ui->listObjects->count() == 0)
                ui->deleteObject->setDisabled(true);
        }
    }
}

void LevelEditor::UpdateLevelSize()
{
    sf::Vector2f size;

    size.x = ui->worldWidth->value();
    size.y = ui->worldHeight->value();
    ui->levelPreview->SetMaxSize(size);
}

bool LevelEditor::Load(const QString &name, const QString &filepath)
{
    DataTree*   file = DataTree::Factory::Yaml(filepath.toStdString());
    Data        fileData(file);
    Data        sizeX, sizeY, gravityX, gravityY, scale, collisionGroups, textures, animations, bodies, entities, landscape;
    Data        hud, script;

    _textureManager.Clear();
    _animationManager.Clear();
    _objectManager.Clear();
    ui->worldLandscape->clear();
    ui->listObjects->clear();
    ui->listTextures->clear();
    ui->shapeEditor->Reset();
    _levelPath    = GetPathFromFilepath(filepath);
    _currentLevel = name;
    _textureManager.SetDirectory(_levelPath + "/../textures/");
    if (!file)
      return (true);
    sizeX           = fileData["size_x"];
    sizeY           = fileData["size_y"];
    gravityX        = fileData["gravity_x"];
    gravityY        = fileData["gravity_y"];
    scale           = fileData["scale"];
    textures        = fileData["textures"];
    animations      = fileData["animations"];
    bodies          = fileData["bodies"];
    entities        = fileData["entities"];
    landscape       = fileData["landscape"];
    collisionGroups = fileData["collisionGroups"];
    hud             = fileData["hud"];
    script          = fileData["script"];
    if (!sizeX.Nil())
        ui->worldWidth->setValue(sizeX);
    if (!sizeY.Nil())
        ui->worldHeight->setValue(sizeY);
    if (!gravityX.Nil())
        ui->gravityX->setValue(gravityX);
    if (!gravityY.Nil())
        ui->gravityY->setValue(gravityY);
    if (!scale.Nil())
        ui->scale->setValue(scale);
    if (!collisionGroups.Nil())
        ui->collisionEditor->Load(collisionGroups);
    if (!hud.Nil())
        ui->fileHud->setText(hud.Value().c_str());
    if (!script.Nil())
    {
        if (!script["source"].Nil() && !script["type"].Nil())
        {
            ui->fileScript->setText(script["source"].Value().c_str());
            if (script["type"].Value() == "cpp")
                ui->scriptType->setText("cpp");
        }
    }
    if (!textures.Nil())
    ui->fileTextures->setText(textures.Value().c_str());
    LoadTextures();
    if (!animations.Nil())
      ui->fileAnimations->setText(animations.Value().c_str());
    LoadAnimations();
    if (!bodies.Nil())
      ui->fileObjects->setText(bodies.Value().c_str());
    LoadObjects();
    if (!entities.Nil())
      ui->levelPreview->Load(_levelPath + PATH_SLASH + entities.Value().c_str());
    if (!landscape.Nil())
    {
        int index;

        for (index = 0 ; index < ui->worldLandscape->count() ; ++index)
        {
            if (ui->worldLandscape->itemText(index) == landscape.Value().c_str())
            {
                ui->worldLandscape->setCurrentIndex(index);
                break ;
            }
        }
    }
    delete file;
    return (true);
}

void LevelEditor::LoadTextures()
{
    if (ui->fileTextures->text() != "")
      _textureManager.Load(_levelPath + PATH_SLASH + ui->fileTextures->text());
}

void LevelEditor::LoadAnimations()
{
    if (ui->fileAnimations->text() != "")
      _animationManager.Load(_levelPath + PATH_SLASH + ui->fileAnimations->text(), &_textureManager);
}

void LevelEditor::LoadObjects()
{
    if (ui->fileObjects->text() != "")
	_objectManager.Load(_levelPath + PATH_SLASH + ui->fileObjects->text());
}

void LevelEditor::Save(const QString& filepath)
{
    YamlStream yaml;
    QPair<QString, float>       scale, gravityX, gravityY, worldHeight, worldWidth;
    QPair<QString, std::string> textures, animations, bodies, entities, landscape, sounds;

    scale.first         = "scale";
    scale.second        = ui->scale->value();
    gravityX.first      = "gravity_x";
    gravityX.second     = ui->gravityX->value();
    gravityY.first      = "gravity_y";
    gravityY.second     = ui->gravityY->value();
    worldHeight.first   = "size_y";
    worldHeight.second  = ui->worldHeight->value();
    worldWidth.first    = "size_x";
    worldWidth.second   = ui->worldWidth->value();

    QString fileTex, fileAnim, fileBodies;

    fileTex    = ui->fileTextures->text();
    fileAnim   = ui->fileAnimations->text();
    fileBodies = ui->fileObjects->text();
    textures.first      = "textures";
    textures.second     = fileTex.length()    ? fileTex.toStdString()    : "textures.yml";
    animations.first    = "animations";
    animations.second   = fileAnim.length()   ? fileAnim.toStdString()   : "animations.yml";
    bodies.first        = "bodies";
    bodies.second       = fileBodies.length() ? fileBodies.toStdString() : "bodies.yml";
    entities.first      = "entities";
    entities.second     = "entities.yml";
    sounds.first        = "sounds";
    sounds.second       = "sounds.yml";

    landscape.first     = "landscape";
    landscape.second    = ui->worldLandscape->currentText().toStdString();

    yaml << worldWidth << worldHeight << gravityX << gravityY << scale;
    yaml << textures << animations << sounds << bodies << entities;
    yaml << landscape;
    ui->collisionEditor->Save(yaml);

    if (ui->fileHud->text().length() > 0)
    {
        QPair<QString, std::string> fileHud("hud", ui->fileHud->text().toStdString());

        yaml << fileHud;
    }
    if (ui->fileScript->text().length() > 0)
    {
        QPair<QString, std::string> fileScript("source", ui->fileScript->text().toStdString());
        QPair<QString, std::string> scriptType("type", ui->scriptType->text().toStdString());

        yaml.newBlock("script");
        yaml << fileScript << scriptType;
        yaml.endBlock();
    }

    if (yaml.SaveToFile(filepath + PATH_SLASH + _currentLevel + PATH_SLASH + "level.yml"))
        return ;
    _textureManager.Save(filepath + PATH_SLASH + _currentLevel + PATH_SLASH + textures.second.c_str());
    _animationManager.Save(filepath + PATH_SLASH + _currentLevel + PATH_SLASH + animations.second.c_str(), &_textureManager);
    _objectManager.Save(filepath + PATH_SLASH + _currentLevel + PATH_SLASH + bodies.second.c_str());
    ui->levelPreview->Save(filepath + PATH_SLASH + _currentLevel + PATH_SLASH + entities.second.c_str());
}

void LevelEditor::TextureListUpdated(void)
{
    QString currentLandscape = ui->worldLandscape->currentText();
    ui->worldLandscape->clear();
    foreach (TextureManager::TexturePair pair, this->_textureManager.GetList())
    {
      ui->worldLandscape->addItem(pair.first);
      if (pair.first == currentLandscape)
        ui->worldLandscape->setCurrentIndex(ui->worldLandscape->count() - 1);
    }
}

void LevelEditor::TextureListIndexChanged(int row)
{
    QString            name = ui->listTextures->item(row)->text();
    const sf::Texture* texture;

    texture = _textureManager.GetByName(name);
    ui->buttonDeleteTexture->setEnabled(true);
    ui->previewTexture->SetTexture(*texture);
}

void LevelEditor::PickNewTexture()
{
    QString filepath = QFileDialog::getOpenFileName(this, "Importer une texture", "");

    if (filepath.length())
    {
      if (!(_textureManager.AddFile(filepath)))
          QMessageBox::warning(this, "Erreur", "Impossible de charger l'image");
    }
}

void LevelEditor::DeleteTexture()
{
    QListWidgetItem* item = ui->listTextures->currentItem();

    if (item)
      _textureManager.DeleteTexture(item->text());
}

void LevelEditor::TextureAdded(QString name)
{
    ui->listTextures->addItem(name);
}

void LevelEditor::CreateNewObject()
{
    QString               name = QInputDialog::getText(this, "Ajouter un objet", "Nom");
    ObjectManager::Object emptyObject;

    if (name == "")
        return ;
    if (_objectManager.GetObject(name))
    {
        QMessageBox::warning(this, "Erreur", "Bitch this name is already taken !");
        return ;
    }
    emptyObject.name             = name;
    emptyObject.hasFixedRotation = false;
    emptyObject.isBullet         = false;
    _objectManager.AddObject(emptyObject);
    ui->shapeEditor->LoadObject(_objectManager.GetObject(name));
}

void LevelEditor::ObjectListUpdated()
{
    const QList<ObjectManager::Object>&    objects = _objectManager.Objects();

    ui->listObjectToAdd->clear();
    ui->listObjects->clear();
    ui->addEntity->setEnabled(false);
    foreach (ObjectManager::Object object, objects)
    {
        ui->listObjectToAdd->addItem(object.name);
        ui->listObjects->addItem(object.name);
        ui->addEntity->setEnabled(true);
    }
}

void LevelEditor::ObjectListIndexChanged(int row)
{
    QListWidgetItem*             item   = ui->listObjects->item(row);

    if (item)
    {
        const QString                name   = item->text();
        ObjectManager::Object*       object = _objectManager.GetObject(name);

        ui->shapeEditor->setEnabled(true);
        ui->shapeEditor->LoadObject(object);
        ui->deleteObject->setEnabled(true);
    }
}

void LevelEditor::Test(const QString& workDir)
{
    QString     tmp     = _currentLevel;
    QString     program = APPLEJACK_SAMPLER_EXE;
    QStringList arguments;
    QProcess    process(this);
    QDir        dir(workDir + "/.test_level");

    if (dir.mkdir(workDir + "/.test_level") || dir.exists())
    {
        _currentLevel = ".test_level";
        Save(workDir);
        arguments << ("./.test_level");
        process.setWorkingDirectory(workDir);
        process.start(program, arguments);
        bool cont = true;
        while (cont)
        {
          process.waitForFinished();
          if (!(process.state() == QProcess::Running))
            cont = false;
        }
        process.waitForFinished();
        if (process.exitStatus() == QProcess::CrashExit)
        {
            QMessageBox::warning(this, "Erreur", "Le client de test a crash");
        }
        qDebug() << "Debugging of the test client :";
        qDebug() << process.readAllStandardOutput();
        qDebug() << process.readAllStandardError();
        dir.rmdir(workDir + "/.test_level");
        _currentLevel = tmp;
    }
    else
        QMessageBox::warning(this, "Erreur", "Impossible de mettre en place l'environnement de test");
}
